
#include "Header.h"
#include "AudioPolationSmoother.h"

#define numcombs 8
#define numallpasses 4
#define muted 0
#define fixedgain 0.015
#define scalewet 3
#define scaledry 2
#define scaledamp 0.4
#define scaleroom 0.28
#define offsetroom 0.7
#define initialroom 0.83
#define initialdamp 0.69
#define initialwet 1 / scalewet
#define initialdry 0.86
#define initialwidth 1
#define initialmode 0
#define freezemode 0.5
#define stereospread 23

// These values assume 44.1KHz sample rate
// they will probably be OK for 48KHz sample rate
// but would need scaling for 96KHz (or other) sample rates.
// The values were obtained by listening tests.
const int combtuningL1 = 1116;
const int combtuningR1 = 1116 + stereospread;
const int combtuningL2 = 1188;
const int combtuningR2 = 1188 + stereospread;
const int combtuningL3 = 1277;
const int combtuningR3 = 1277 + stereospread;
const int combtuningL4 = 1356;
const int combtuningR4 = 1356 + stereospread;
const int combtuningL5 = 1422;
const int combtuningR5 = 1422 + stereospread;
const int combtuningL6 = 1491;
const int combtuningR6 = 1491 + stereospread;
const int combtuningL7 = 1557;
const int combtuningR7 = 1557 + stereospread;
const int combtuningL8 = 1617;
const int combtuningR8 = 1617 + stereospread;
const int allpasstuningL1 = 556;
const int allpasstuningR1 = 556 + stereospread;
const int allpasstuningL2 = 441;
const int allpasstuningR2 = 441 + stereospread;
const int allpasstuningL3 = 341;
const int allpasstuningR3 = 341 + stereospread;
const int allpasstuningL4 = 225;
const int allpasstuningR4 = 225 + stereospread;

struct comb
{
	double feedback;
	double filterstore;
	double damp1;
	double damp2;
	double* buffer;
	int bufsize;
	int bufidx;

	void setbuffer(double* buf, int size)
	{
		filterstore = 0;
		bufidx = 0;
		buffer = buf;
		bufsize = size;
	}

	void mute()
	{
		for (int i = 0; i < bufsize; i++)
			buffer[i] = 0;
	}

	void setdamp(double val)
	{
		damp1 = val;
		damp2 = 1 - val;
	}

	double getdamp()
	{
		return damp1;
	}

	void setfeedback(double val)
	{
		feedback = val;
	}

	double getfeedback()
	{
		return feedback;
	}

	double process(double& input)
	{
		double output = buffer[bufidx];
		//Undenormalise.undenormalise(ref output);
		if (output < 0.0000001 && output > -0.0000001) output = 0.0;

		filterstore = (output * damp2) + (filterstore * damp1);
		//Undenormalise.undenormalise(ref filterstore);
		if (filterstore < 0.0000001 && filterstore > -0.0000001) filterstore = 0.0;

		buffer[bufidx] = input + (filterstore * feedback);

		if (++bufidx >= bufsize) bufidx = 0;

		return output;
	}

	void cleanAudio()
	{
		for (int i = 0; i < bufsize; ++i)
		{
			if (buffer[i] < 0.0000001 && buffer[i] > -0.0000001) buffer[i] = 0.0;
		}
	}

	void clear()
	{
		//Array.Clear(buffer, 0, buffer.Length);
		for (int i = 0; i < bufsize; i++)
		{
			buffer[i] = 0;
		}
	}
};

struct allpass
{
	double* buffer;
	int bufsize;
	double feedback;
	int bufidx;

	void setbuffer(double* buf, int size)
	{
		bufidx = 0;
		buffer = buf;
		bufsize = size;
	}

	void mute()
	{
		for (int i = 0; i < bufsize; i++)
			buffer[i] = 0;
	}

	void setfeedback(double val)
	{
		feedback = val;
	}

	double getfeedback()
	{
		return feedback;
	}

	void process(double& input)
	{
		double bufout = buffer[bufidx];
		//Undenormalise.undenormalise(ref bufout);
		if (bufout < 0.0000001 && bufout > -0.0000001) bufout = 0.0;

		double _output = -input + bufout;
		buffer[bufidx] = input + (bufout * feedback);

		if (++bufidx >= bufsize) bufidx = 0;

		input = _output;
	}

	void cleanAudio()
	{
		for (int i = 0; i < bufsize; ++i)
		{
			if (buffer[i] < 0.0000001 && buffer[i] > -0.0000001) buffer[i] = 0.0;
		}
	}

	void clear()
	{
		//Array.Clear(buffer, 0, buffer.Length);
		for (int i = 0; i < bufsize; i++)
		{
			buffer[i] = 0;
		}
	}
};

struct ReverbDataStruct
{
	int channels;
	int Length;
} typedef ReverbData;

class ReverbPtr
{
public:
	AudioPolationSmoother smoother;

	// Comb filters
	comb combL[numcombs];
	comb combR[numcombs];

	// Allpass filters
	allpass allpassL[numallpasses];
	allpass allpassR[numallpasses];

	// Buffers for the combs
	double bufcombL1[combtuningL1];
	double bufcombR1[combtuningR1];
	double bufcombL2[combtuningL2];
	double bufcombR2[combtuningR2];
	double bufcombL3[combtuningL3];
	double bufcombR3[combtuningR3];
	double bufcombL4[combtuningL4];
	double bufcombR4[combtuningR4];
	double bufcombL5[combtuningL5];
	double bufcombR5[combtuningR5];
	double bufcombL6[combtuningL6];
	double bufcombR6[combtuningR6];
	double bufcombL7[combtuningL7];
	double bufcombR7[combtuningR7];
	double bufcombL8[combtuningL8];
	double bufcombR8[combtuningR8];

	// Buffers for the allpasses
	double bufallpassL1[allpasstuningL1];
	double bufallpassR1[allpasstuningR1];
	double bufallpassL2[allpasstuningL2];
	double bufallpassR2[allpasstuningR2];
	double bufallpassL3[allpasstuningL3];
	double bufallpassR3[allpasstuningR3];
	double bufallpassL4[allpasstuningL4];
	double bufallpassR4[allpasstuningR4];

	ReverbPtr()
		: smoother(2000)
	{
		// Tie the components to their buffers
		combL[0].setbuffer(bufcombL1, combtuningL1);
		combR[0].setbuffer(bufcombR1, combtuningR1);
		combL[1].setbuffer(bufcombL2, combtuningL2);
		combR[1].setbuffer(bufcombR2, combtuningR2);
		combL[2].setbuffer(bufcombL3, combtuningL3);
		combR[2].setbuffer(bufcombR3, combtuningR3);
		combL[3].setbuffer(bufcombL4, combtuningL4);
		combR[3].setbuffer(bufcombR4, combtuningR4);
		combL[4].setbuffer(bufcombL5, combtuningL5);
		combR[4].setbuffer(bufcombR5, combtuningR5);
		combL[5].setbuffer(bufcombL6, combtuningL6);
		combR[5].setbuffer(bufcombR6, combtuningR6);
		combL[6].setbuffer(bufcombL7, combtuningL7);
		combR[6].setbuffer(bufcombR7, combtuningR7);
		combL[7].setbuffer(bufcombL8, combtuningL8);
		combR[7].setbuffer(bufcombR8, combtuningR8);
		allpassL[0].setbuffer(bufallpassL1, allpasstuningL1);
		allpassR[0].setbuffer(bufallpassR1, allpasstuningR1);
		allpassL[1].setbuffer(bufallpassL2, allpasstuningL2);
		allpassR[1].setbuffer(bufallpassR2, allpasstuningR2);
		allpassL[2].setbuffer(bufallpassL3, allpasstuningL3);
		allpassR[2].setbuffer(bufallpassR3, allpasstuningR3);
		allpassL[3].setbuffer(bufallpassL4, allpasstuningL4);
		allpassR[3].setbuffer(bufallpassR4, allpasstuningR4);

		// Set default values
		allpassL[0].setfeedback(0.5f);
		allpassR[0].setfeedback(0.5f);
		allpassL[1].setfeedback(0.5f);
		allpassR[1].setfeedback(0.5f);
		allpassL[2].setfeedback(0.5f);
		allpassR[2].setfeedback(0.5f);
		allpassL[3].setfeedback(0.5f);
		allpassR[3].setfeedback(0.5f);
		//SetWet(initialwet);
		//SetRoomsSize(initialroom);
		//SetDry(initialdry);
		//SetDamp(initialdamp);
		//SetWidth(initialwidth);
		//SetMode((int)initialmode);
	}

	double gain;
	double roomsize, roomsize1;
	double damp, damp1;
	double wet, wet1, wet2;
	double dry;
	double width;
	double mode;

	double mRoomsSize;
	double mDamp;
	double mWet;
	double mDry;
	double mWidth;
	int mMode;

	void SetRoomsSize(double value)
	{
		mRoomsSize = value;
		roomsize = (value * scaleroom) + offsetroom;
	}

	void SetDamp(double value)
	{
		mDamp = value;
		damp = value * scaledamp;
	}

	void SetWet(double value)
	{
		mWet = value;
		wet = value * scalewet;
	}

	void SetDry(double value)
	{
		mDry = value;
		dry = value * scaledry;
	}

	void SetWidth(double value)
	{
		mWidth = value;
		width = value;
	}

	void SetMode(double value)
	{
		mMode = value;
		mode = value;
	}

	// Recalculate internal values after parameter change
	void Update()
	{
		int i;

		wet1 = wet * (width / 2 + 0.5f);
		wet2 = wet * ((1 - width) / 2);

		if (mode >= freezemode)
		{
			roomsize1 = 1;
			damp1 = 0;
			gain = muted;
		}
		else
		{
			roomsize1 = roomsize;
			damp1 = damp;
			gain = fixedgain;
		}

		for (i = 0; i < numcombs; i++)
		{
			combL[i].setfeedback(roomsize1);
			combR[i].setfeedback(roomsize1);
		}

		for (i = 0; i < numcombs; i++)
		{
			combL[i].setdamp(damp1);
			combR[i].setdamp(damp1);
		}
	}

	void ClearReverb()
	{
		combL[0].clear();
		combL[0].clear();
		combR[0].clear();
		combL[1].clear();
		combR[1].clear();
		combL[2].clear();
		combR[2].clear();
		combL[3].clear();
		combR[3].clear();
		combL[4].clear();
		combR[4].clear();
		combL[5].clear();
		combR[5].clear();
		combL[6].clear();
		combR[6].clear();
		combL[7].clear();
		combR[7].clear();
		allpassL[0].clear();
		allpassR[0].clear();
		allpassL[1].clear();
		allpassR[1].clear();
		allpassL[2].clear();
		allpassR[2].clear();
		allpassL[3].clear();
		allpassR[3].clear();
	}
};

extern "C"
{
	DECLSPEC ReverbPtr* STDCALL Effect_Reverb_Create()
	{
		return new ReverbPtr();
	}

	DECLSPEC void STDCALL Effect_Reverb_Delete(ReverbPtr* ptr)
	{
		delete ptr;
	}

	DECLSPEC void STDCALL Effect_Reverb_Setup(ReverbPtr* ptr, double roomsize, double damp, double wet, double dry, double width, int mode)
	{
		ptr->SetRoomsSize(roomsize);
		ptr->SetDamp(damp);
		ptr->SetWet(wet);
		ptr->SetDry(dry);
		ptr->SetWidth(width);
		ptr->SetMode(mode);
		ptr->Update();

	}

	DECLSPEC void STDCALL Effect_Reverb_Clear(ReverbPtr* ptr)
	{
		ptr->ClearReverb();
	}

	DECLSPEC void STDCALL Effect_Reverb_Process(
		ReverbPtr* ptr, ReverbDataStruct* data,
		double* wav_in_left, double* wav_in_right, double* wav_out_left, double* wav_out_right)
	{
		double inputL;
		double inputR;
		double outL;
		double outR;
		double input;

		if (data->channels == 2)
		{
			comb* combL = ptr->combL;
			comb* combR = ptr->combR;
			allpass* allpassL = ptr->allpassL;
			allpass* allpassR = ptr->allpassR;
			double wet1 = ptr->wet1;
			double wet2 = ptr->wet2;
			double dry = ptr->dry;
			double gain = ptr->gain;
			int length = data->Length;

			for (int l = 0; l < length; l++)
			{
				inputL = wav_in_left[l];
				inputR = wav_in_right[l];

				outL = outR = 0;
				input = (inputL + inputR) * gain;

				// Accumulate comb filters in parallel
				for (int i = 0; i < numcombs; i++)
				{
					outL += combL[i].process(input);
					outR += combR[i].process(input);
				}

				// Feed through allpasses in series
				for (int i = 0; i < numallpasses; i++)
				{
					allpassL[i].process(outL);
					allpassR[i].process(outR);
				}

				// Calculate output MIXING with anything already there
				wav_out_left[l] = outL * wet1 + outR * wet2 + inputL * dry;
				wav_out_right[l] = outR * wet1 + outL * wet2 + inputR * dry;
			}

			ptr->smoother.Process(wav_out_left, data->Length);
			ptr->smoother.Process(wav_out_right, data->Length);
		}
		else
		{
			for (int l = 0; l < data->Length; l++)
			{
				inputL = wav_in_left[l];

				outL = outR = 0;
				input = inputL * ptr->gain;

				// Accumulate comb filters in parallel
				for (int i = 0; i < numcombs; i++)
				{
					outL += ptr->combL[i].process(input);
				}

				// Feed through allpasses in series
				for (int i = 0; i < numallpasses; i++)
				{
					ptr->allpassL[i].process(outL);
				}

				// Calculate output MIXING with anything already there
				wav_out_left[l] = outL * ptr->wet1 + outR * ptr->wet2 + inputL * ptr->dry;
			}
		}
	}
}